using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Threading;
using System.Resources;
using System.Reflection;
using System.Diagnostics;
using System.Configuration;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.TransportProxy.Interop;

namespace Microsoft.Samples.BizTalk.Adapter.Tcp.Send
{
    /// <summary>
    ///     This class implements a send adapter in BizTalk.
    /// </summary>
    /// <author>
    ///     Paul Brown
    /// </author>
    public partial class SendAdapter : IBaseComponent,
                                       IBTTransport,
                                       IPersistPropertyBag,
                                       IBTTransportControl,
                                       IBTBatchTransmitter,
                                       IAdapter
    {
        #region Public Static Members

        public static readonly string BizTalkManagementDatabaseConnectionString = ConfigurationManager.ConnectionStrings["BizTalkManagementDatabase"].ConnectionString;

        #endregion

        #region Private Instance Members

        private Guid                                _clsid;                 // The class ID of this adapter
        private string                              _transportType;         // The transport type of this adapter
        private IBTTransportProxy                   _proxy;                 // Our transport proxy used to interact with messaging engine
        private TraceSwitch                         _sendSwitch;            // Trace switch for send adapter
        private TraceSwitch                         _socketSwitch;          // Trace switch for socket work
        private int                                 _sendTransmitBatchSize; // Determines the number of messages submitted by BizTalk to the adapter per batch
        private int                                 _sendResponseBatchSize; // Determines the number of response messages submitted to BizTalk per batch
        private int                                 _sendBatchTimeout;      // Specifies the number of milliseconds that must elapse before a batch is submitted
        private int                                 _sendBatchMaxData;      // Specifies the maximum batch size that is submitted to BizTalk (if a single message exceeds this it will be submitted in its own batch)
        private int                                 _maxBatchWorkerThreads; // The number of batch worker threads
        private int                                 _maxDuplexWorkerThreads;// The number of duplex worker threads
        private int                                 _maxTransmitWorkerThreads;// The number of transmit worker threads
        private int                                 _batchRetryCount;       // Batch retry count
        private int                                 _batchRetryInterval;    // Batch retry interval in seconds
        private Dispatcher                          _dispatcher;            // The work dispatcher
        private string                              _deadMessageFolder;     // The location where dead messages are saved
        private bool                                _lazyInitDispatcher;    // Determines whether dispatcher should be lazily initialized
        private int                                 _threadIdleTimeout;     // Determines the timeout in minutes that a thread must be idle before being shut down

        #endregion

        #region Constructor

        /// <summary>
        ///     This is the default constructor for the class.
        /// </summary>
        public SendAdapter()
        {
            // Initialize members
            this._clsid = new Guid("3AEC8641-56DC-457e-B814-FDAA85BD0A52");
            this._transportType = "TCP";
            this._proxy = null;
            this._sendTransmitBatchSize = 10;
            this._sendResponseBatchSize = 10;
            this._sendBatchTimeout = 250;
            this._sendBatchMaxData = 1024;
            this._maxBatchWorkerThreads = 5;
            this._maxDuplexWorkerThreads = 5;
            this._maxTransmitWorkerThreads = 10;
            this._batchRetryCount = 5;
            this._batchRetryInterval = 10;
            this._sendSwitch = new TraceSwitch("SendAdapter", TraceHelper.ResourceManager.GetString(ResourceConstants.TraceSwitchSendAdapterDescription), "3");
            this._socketSwitch = new TraceSwitch("Socket", TraceHelper.ResourceManager.GetString(ResourceConstants.TraceSwitchSocketDescription), "0");
            this._dispatcher = null;
            this._deadMessageFolder = string.Empty;
            this._lazyInitDispatcher = true;
            this._threadIdleTimeout = 5;
        }

        #endregion

        #region Public Instance Properties

        /// <summary>
        ///     This property returns the maximum size of the batch that represents the maximum number of messages
        ///     BizTalk will give to the adapter per batch.
        /// </summary>
        public int TransmitBatchSize
        {
            get { return (this._sendTransmitBatchSize); }
        }

        /// <summary>
        ///     This property returns the maximum size of the send batch that represents that maximum number of
        ///     response messages to submit per batch.
        /// </summary>
        public int ResponseBatchSize
        {
            get { return (this._sendResponseBatchSize); }
        }

        /// <summary>
        ///     This property returns the maximum timeout in milliseconds that represents that amount of time that must
        ///     pass before a batch is submitted regardless of whether it is full or not.
        /// </summary>
        public int BatchTimeout
        {
            get { return (this._sendBatchTimeout); }
        }

        /// <summary>
        ///     This property returns the maximum KB in a batch that represents the maximum size of the data added to a batch
        ///     before it is submitted regardless of whether the batch is full or not.
        /// </summary>
        public int MaxBatchData
        {
            get { return (this._sendBatchMaxData); }
        }

        /// <summary>
        ///     This property returns the maximum number of worker threads that are used to process batches.
        /// </summary>
        public int MaxBatchWorkerThreads
        {
            get { return (this._maxBatchWorkerThreads); }
        }

        /// <summary>
        ///     This property returns the maximum number of worker threads that are used to send messages to duplex listeners.
        /// </summary>
        public int MaxDuplexWorkerThreads
        {
            get { return (this._maxDuplexWorkerThreads); }
        }

        /// <summary>
        ///     This property returns the maximum number of worker threads that are used to send messages to remote endpoints.
        /// </summary>
        public int MaxTransmitWorkerThreads
        {
            get { return (this._maxTransmitWorkerThreads); }
        }

        /// <summary>
        ///     This property returns the retry count that the adapter uses to retry failed batch submissions.
        /// </summary>
        public int BatchRetryCount
        {
            get { return (this._batchRetryCount); }
        }

        /// <summary>
        ///     This property returns the interval in seconds that the adapter waits between retries.
        /// </summary>
        public int BatchRetryInterval
        {
            get { return (this._batchRetryInterval); }
        }

        /// <summary>
        ///     This property determines whether the dispatcher should be lazily initialized.
        /// </summary>
        public bool LazyInitializeDispatcher
        {
            get { return (this._lazyInitDispatcher); }
        }

        /// <summary>
        ///     This property returns the number of minutes that a thread must be idle before being shut down.
        /// </summary>
        public int ThreadIdleTimeout
        {
            get { return (this._threadIdleTimeout); }
        }

        #endregion

        #region Private Instance Methods

        /// <summary>
        ///     This method re-initializes the adapter state.
        /// </summary>
        private void ReInitialize()
        {
            // Re-create the dispatcher
            this._dispatcher = new Dispatcher(this, this.ThreadIdleTimeout);
        }

        /// <summary>
        ///     This method loads the handler configuration.
        /// </summary>
        private void LoadSendHandlerConfiguration(IPropertyBag adapterConfiguration)
        {
            // Get XML document containing adapter configuration
            XmlDocument handlerConfiguration = ConfigurationHelper.LoadAdapterConfiguration(this, adapterConfiguration);
            if (handlerConfiguration == null)
            {
                // No configuration, default
                return;
            }

            // Transmit Batch Size
            XmlNode node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/sendTransmitBatchSize");
            this._sendTransmitBatchSize = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendTransmitBatchSize, this.TransmitBatchSize);

            // Response Batch Size
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/sendResponseBatchSize");
            this._sendResponseBatchSize = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendResponseBatchSize, this.ResponseBatchSize);

            // Batch Submission Timeout
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/sendBatchTimeout");
            this._sendBatchTimeout = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendBatchSubmissionTimeout, this.BatchTimeout);

            // Max Batch KB Size
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/sendBatchMaxDataSize");
            this._sendBatchMaxData = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendMaxBatchKbSize, this.MaxBatchData);

            // Batch Retry Count
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/batchRetryCount");
            this._batchRetryCount = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendBatchRetryCount, this._batchRetryCount);

            // Batch Retry Interval
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/batchRetryInterval");
            this._batchRetryInterval = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendBatchRetryInterval, this._batchRetryInterval);

            // Send Batch Worker Threads
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/sendBatchWorkerThreads");
            this._maxBatchWorkerThreads = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendBatchWorkerThreads, this.MaxBatchWorkerThreads);

            // Send Duplex Worker Threads
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/sendDuplexWorkerThreads");
            this._maxDuplexWorkerThreads = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendDuplexWorkerThreads, this.MaxDuplexWorkerThreads);

            // Send Transmit Worker Threads
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/sendTransmitWorkerThreads");
            this._maxTransmitWorkerThreads = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendTransmitWorkerThreads, this.MaxTransmitWorkerThreads);

            // Dead Message Folder
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/deadMessageFolder");
            this._deadMessageFolder = node.InnerText;
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendDeadMessageFolder, this.DeadMessageFolder);

            // Lazy Initialize Dispatcher
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/threadLazyInitialization");
            this._lazyInitDispatcher = bool.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendLazyInitializeDispatcher, this.LazyInitializeDispatcher);

            // Thread Idle Timeout
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/threadIdleTimeout");
            this._threadIdleTimeout = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendThreadIdleTimeout, this.ThreadIdleTimeout);
        }

        #endregion
    }
}
